package com.example.springboot_demo.searcher;

import org.ansj.domain.Term;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

import static org.ansj.splitWord.analysis.ToAnalysis.parse;

public class DocSearcher {
    //停用词文件的路径
    private static String STOP_WORD_PATH = null;
     //       "usr\\local\\doc_searcher_index\\stop.txt\\";
    //用hashset保存停用词
    static {
        if(Config.isOnline){
            STOP_WORD_PATH = "usr\\local\\doc_searcher_index\\stop.txt\\";
        }else{
            STOP_WORD_PATH = "D:\\apache-tomcat-8.5.87\\webapps\\";

        }
     }
    private HashSet<String> stopWords = new HashSet<>();//
    //此处要加上索引对象的实例
    //同时要完成索引加载的工作
    private  Index index = new Index();//

    public DocSearcher() {
        index.load();
    }

    //完成整个搜索过程的方法
    //参数(输入部分)就是用户给出的查询词
    //返回值(输出部分)就是搜索结果的集合

    public  ArrayList<Result> search(String query) {//
        //1.[分词]针对query这个查询词进行分词
        List<Term> oldTerms = parse(query).getTerms();
        List<Term> terms = new ArrayList<>();

        for (Term term : oldTerms) {
            if (stopWords.contains(term.getName())) {
                continue;
            }
            terms.add(term);
        }
        //2.[触发]针对分词结果来查倒排
        List<List<Weight>> termResult = new ArrayList<>();
        for (Term term : terms) {
            String word = term.getName();
            //虽然倒排索引中,有很多的词,但是这里的词一定都是之前的文档中存在的
            List<Weight> invertedList = index.getInverted(word);

            if (invertedList == null) {
                //说明这个词在所有文档中都不存在

                continue;
            }
            //追加一组元素,就是查到的元素放到allTermReasult里汇总
            termResult.add(invertedList);

        }

//3.[合并]针对多个分词结果触发出相同文档,进行权重合并
        List<Weight> allTermResult = mergeResult(termResult);


        //4.[排序]针对触发的结果按照权重降序排序
        allTermResult.sort(new Comparator<Weight>() {
            @Override//创建一个实现comparator接口的类,然后重写comparator里面的方法
            //然后再去new出类里面的实例来
            public int compare(Weight o1, Weight o2) {
                //这里建议不要去背,可以随机写上运行着试一下
                //如果是升序:return o1.getWeight() - o2.getWeight();
                //如果是降序:return o2.getWeight() - o1.getWeight();
                return o2.getWeight() - o1.getWeight();
            }
        });
        //5.[包装结果]针对排序的结果,去查正排,构造出要返回的数据

        ArrayList<Result> results = new ArrayList<>();
        for (Weight weight : allTermResult) {
            DocInfo docInfo = index.getDocInfo(weight.getDocId());
            Result result = new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
            result.setDesc(GenDesc(docInfo.getContent(), terms));
            results.add(result);
        }
//        System.out.println("11111111111111111");
//        System.out.println(results);
        return results;

    }

    //通过这个内部类,来描述一个元素在二维数组中的位置
    static class Pos {
        public int row;
        public int col;

        public Pos(int row, int col) {
            this.row = row;
            this.col = col;
        }
    }

    private static List<Weight> mergeResult(List<List<Weight>> source) {//
        //在进行合并的时候,是把多个行合并成一行了
        //合并过程中势必是需要操作这个二维数组(二维List)里面的每个元素的...
        //操作元素就涉及到"行""列这样的概念~~想要确定二维数组的一个元素,就需要明确的知道 行 和 列
        //1.针对每一行进行排序(按照ID进行升序排序)
        //2.借助一个优先队列,针对这些进行合并
        for (List<Weight> curRow : source) {
            curRow.sort(new Comparator<Weight>() {
                @Override//这里定义排序是升序
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId() - o2.getDocId();
                }
            });
        }


        // target表示合并的结果
        List<Weight> target = new ArrayList<>();
        //2.1创建优先级队列,并指定比较规则(按照Weight的docId,取小的优先
        PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                //先根据POS值找对应的Weight对象,再根据Weight的docid来排序
                Weight w1 = source.get(o1.row).get(o1.col);
                Weight w2 = source.get(o2.row).get(o2.col);
                return w1.getDocId() - w2.getDocId();
            }
        });
        //2.2初始化队列,把每一行的第一个元素放到队列中
        for (int row = 0; row < source.size(); row++) {
//初始插入的元素从col就是0
            queue.offer(new Pos(row, 0));
        }
        //2.3循环的取对手元素(也就是当前这若干行中最小的元素
        while (!queue.isEmpty()) {
            Pos minPos = queue.poll();
            Weight curWeight = source.get(minPos.row).get(minPos.col);
            //2.4看看这个取到的weight是否和前一个插入到target中的结果是相同的docid
            //如果是,就合并
            if (target.size() > 0) {
                //取出来上次插入的元素
                Weight lastWeight = target.get(target.size() - 1);
                if (lastWeight.getDocId() == curWeight.getWeight()) {
                    //说明遇到了相同的文档
                    //合并权重
                    lastWeight.setWeight(lastWeight.getWeight() + curWeight.getWeight());
                } else {
                    //如果文档ID不相同,就直接把curWeight给插入到target的末尾
                    target.add(curWeight);
                }
            } else {
                //如果target当前是空的,就直接插
                target.add(curWeight);

            }//把当前元素处理完之后,要把对应的这个元素的光标后移,去取这一行的下一个元素
            Pos newPos = new Pos(minPos.row, minPos.col + 1);
            if (newPos.col >= source.get(newPos.row).size()) {
//如果移动光标之后,超出了这一行的列数,就说明到达末尾了
                //到达末尾之后说明这一行就处理完毕
                continue;
            }
            queue.offer(newPos);//还有可以取的
        }
        return target;
    }
    private static String GenDesc(String content,List<Term> terms) {//
        //先遍历分词结果,看看哪个结果是在content中存在
        int firstPos = -1;
        for (Term term : terms) {
            //别忘了,分词库直接针对词进行转小写
            //正因为如此,就必须把正文也先转成小写,然后再查询
            String word = term.getName();
            content = content.toLowerCase().replaceAll("\\b"+word +"\\b"," "+word+" ");
            //此处需要"全词匹配",让Word能够独立成词,才要查出来,而不只作为此的一部分
            firstPos = content.toLowerCase().indexOf(" " +word+" ");
            if (firstPos >= 0) {
                //找到位置
                break;
            }

        }
        //后面还需要用到这个firstPos
        if(firstPos == -1){
            //所有分词结果都不在正文中存在,但是在标题中存在
            if(content.length()>160){
                return content.substring(0,160)+"...";
            }
            return content;
        }
        //从firstPos作为基准位置,往前找60个字符,作为描述的起始位置
        String desc = " ";
        int descBeg = firstPos < 60 ? 0 : firstPos - 60;
        if(descBeg+160>content.length()){
            desc = content.substring(descBeg);
            //substring一个参数就是从参数位置截到末尾

        }else{
            desc = content.substring(descBeg,descBeg +160)+"...";
        }
        //在此处加上一个替换操作,
        // 在描述中的和分词结果相同的部分,给加上一层<i>,可以通过replace方式实现
        for (Term term:terms) {
            String word = term.getName();
            //注意,此处全字匹配,也就是查询词为List的时候,不能把Arraylist中的List给单独标红
          desc= desc.replaceAll("(?i) "+word+" ","<i>"+word+"</i>");
        }
        return desc;
    }

public void loadStopWords(){
try(BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_WORD_PATH))){
 while (true){
     String line = bufferedReader.readLine();//这个readline不读入斜杠
     if(line == null){
         //读取文件完毕
         break;
     }
     stopWords.add(line);
 }
}catch(IOException e){
    e.printStackTrace();
}


}



    public static void main(String[] args) {
        DocSearcher docSearcher = new DocSearcher();
        Scanner scanner = new Scanner(System.in);
        while(true){
            System.out.print("->");
        //    String query = scanner.next();
          ArrayList<Result> results = docSearcher.search("arraylist");
            for (Result result:results) {
                System.out.println("==================================");
                System.out.println(result);

            }
        }
    }
}
